package com.netty.client;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioChannelOption;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.LengthFieldPrepender;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.timeout.IdleStateHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.InetSocketAddress;
import java.nio.charset.Charset;
import java.util.concurrent.TimeUnit;

public class NettyClientStarter {
    private static final Logger log = LoggerFactory.getLogger(NettyClientStarter.class);
    /**
     * maxFrameLength - 发送的数据包最大长度；
     */
    private static final int maxFrameLength = Integer.MAX_VALUE;
    /**
     * lengthFieldOffset - 长度域偏移量，指的是长度域位于整个数据包字节数组中的下标；
     */
    private static final int lengthFieldOffset = 0;
    /**
     * lengthFieldLength - 长度域的自己的字节数长度。
     */
    private static final int lengthFieldLength = 4;
    /**
     * lengthAdjustment – 长度域的偏移量矫正。 如果长度域的值，除了包含有效数据域的长度外，还包含了其他域（如长度域自身）长度，那么，就需要进行矫正。矫正的值为：包长 - 长度域的值 – 长度域偏移 – 长度域长
     */
    private static final int lengthAdjustment = 0;
    /**
     * initialBytesToStrip – 丢弃的起始字节数。丢弃处于有效数据前面的字节数量。比如前面有4个节点的长度域，则它的值为4。
     */
    private static final int initialBytesToStrip = lengthFieldLength;
    /**
     *
     */
    private static final boolean failFast = true;

    private String host = "127.0.0.1";
    private int port = 5510;

    private boolean isStart = false;

    Bootstrap bootstrap = new Bootstrap();
    private EventLoopGroup workGroup = new NioEventLoopGroup();

    public void clientStart() {

        if (isStart) return;

        workGroup = new NioEventLoopGroup();

        bootstrap = new Bootstrap();
        bootstrap.channel(NioSocketChannel.class);
        bootstrap.group(workGroup);
        bootstrap.option(NioChannelOption.SO_REUSEADDR, true)
                .option(NioChannelOption.TCP_NODELAY, true)
                .option(NioChannelOption.SO_KEEPALIVE, true);
        bootstrap.handler(new ChannelInitializer<SocketChannel>() {
            @Override
            protected void initChannel(SocketChannel socketChannel) throws Exception {
                ChannelPipeline pipeline = socketChannel.pipeline();
                pipeline.addLast(new IdleStateHandler(0, 0, 10, TimeUnit.SECONDS));
                pipeline.addLast(new LoggingHandler(LogLevel.DEBUG));
                pipeline.addLast(new LengthFieldBasedFrameDecoder(maxFrameLength, lengthFieldOffset, lengthFieldLength,
                        lengthAdjustment, initialBytesToStrip, failFast));
                pipeline.addLast(new StringDecoder(Charset.forName("UTF-8")));
                pipeline.addLast(new LengthFieldPrepender(lengthFieldLength));
                pipeline.addLast(new StringEncoder(Charset.forName("UTF-8")));
                pipeline.addLast(new NettyClientHandler());
            }
        });
        bootstrap.remoteAddress(new InetSocketAddress(host, port));
        doConnect();
    }

    public void doConnect() {
        bootstrap.connect().addListener((ChannelFuture f) -> {
            if (!f.isSuccess()) {
                log.info("连接服务端失败，5s后重连...");
                final EventLoop loop = f.channel().eventLoop();
                loop.schedule(new Runnable() {
                    @Override
                    public void run() {
                        doConnect();
                    }
                }, 5L, TimeUnit.SECONDS);
            } else {
                log.info("建立连接，连接服务端成功！");
                isStart = true;
            }
        });
    }

    public synchronized void clientStop() {
        try {
            if (!isStart) return;

            log.info("关闭连接！");
            workGroup.shutdownGracefully().sync();
            isStart = false;
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        System.setProperty("io.netty.loggerFactory", "slf4j");
        new NettyClientStarter().clientStart();
    }
}
